package com.example.demo.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.base.config.auth.UserInfoShareHolder;
import com.example.demo.base.login.dto.UserCache;
import com.example.demo.constants.BusinessExceptionCode;
import com.example.demo.constants.Constants;
import com.example.demo.constants.ProblemPublicConstant;
import com.example.demo.constants.RedisKey;
import com.example.demo.domain.entity.*;
import com.example.demo.dto.ProblemViewDto;
import com.example.demo.dto.ProgrammingQuestionDTO;
import com.example.demo.dto.VojProblemLanguageDto;
import com.example.demo.enums.UserIdentifyEnum;
import com.example.demo.exception.BusinessException;
import com.example.demo.exception.ExceptionUtils;
import com.example.demo.mapper.*;
import com.example.demo.dto.ProblemViewPageDto;
import com.example.demo.param.voj.VojCreateProblemParam;
import com.example.demo.param.voj.VojProblemLanguageParam;
import com.example.demo.param.voj.VojProblemQueryParam;
import com.example.demo.service.NewProblemService;
import com.example.demo.utils.RedisUtil;
import com.example.demo.utils.SlugifyUtils;
import com.example.demo.utils.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

@Service
@Transactional
public class NewProblemServiceImpl implements NewProblemService {

    private static final Logger LOGGER = LoggerFactory.getLogger(NewProblemServiceImpl.class);

    @Resource
    private NewProblemMapper newProblemMapper;

    @Resource
    private ProblemCategoryMapper problemCategoryMapper;

    @Resource
    private ProblemTagMapper problemTagMapper;

    @Resource
    private CheckpointMapper checkpointMapper;

    @Resource
    private ProblemFileMapper problemFileMapper;
    @Resource
    private VojProblemCheckpointsMapper vojProblemCheckpointsMapper;

    @Resource
    private UserMapper userMapper;

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private VojProblemLanguageMapper vojProblemLanguageMapper;
    @Resource
    private VojProblemsMapper vojProblemsMapper;


    /**
     * 保存题目
     * @param param
     * @return
     */
    @Override
    public Long save(VojCreateProblemParam param) {
        Long companyId = UserInfoShareHolder.getUserInfo().getCompanyId();
        param.setCompanyId(companyId);
        VojProblems newProblem = BeanUtil.copyProperties(param, VojProblems.class);

        
        if (null == newProblem.getProblemId()){
            // 新增
            vojProblemsMapper.insert(newProblem);
            createProblemLanguage(newProblem.getProblemId(), param.getLanguageList());
            createTestCases(newProblem.getProblemId(), param.getTestCases(), param.isExactlyMatch());
            createProblemCategoryRelationships(newProblem.getProblemId(), param.getProblemCategories());
            createProblemTags(newProblem.getProblemId(), param.getProblemTags());
            createProblemFile(newProblem.getProblemId(), param.getProblemFiles());
        }else {
            Long problemId = newProblem.getProblemId();
            // 修改
            vojProblemsMapper.updateById(newProblem);
            updateTestCases(problemId, param.getTestCases(), param.isExactlyMatch());
            updateProblemCategoryRelationships(problemId, param.getProblemCategories());
            updateProblemTags(problemId, param.getProblemTags());
            updateProblemLanguage(problemId, param.getLanguageList());
        }
        
        return newProblem.getProblemId();
    }
    


    /**
     * 删除指定的试题.
     * @param problemId - 试题的唯一标识符
     */
    @Override
    public int delete(long problemId) {
        return vojProblemsMapper.deleteById(problemId);
    }


    /**
     * 创建【题目-语言-时间内存限制】
     * @param problemId
     * @param languageList
     */
    private void createProblemLanguage(long problemId, List<VojProblemLanguageParam> languageList) {
        if (CollectionUtil.isEmpty(languageList)){
            throw new BusinessException(BusinessExceptionCode.LIST_NULL);
        }
        languageList.stream().forEach(setting -> {
            VojProblemLanguage language = BeanUtil.copyProperties(setting, VojProblemLanguage.class);
            language.setProblemId(problemId);
            vojProblemLanguageMapper.insert(language);
        });
    }

    /**
     * 创建【测试用例】
     * @param problemId - 试题的唯一标识符
     * @param testCases - 测试用例 (JSON格式)
     * @param isExactlyMatch - 是否精确匹配测试用例
     */
    private void createTestCases(long problemId, String testCases, boolean isExactlyMatch) {
        JSONArray jsonArray = JSON.parseArray(testCases);

        for ( int i = 0; i < jsonArray.size(); ++ i ) {
            JSONObject testCase = jsonArray.getJSONObject(i);

            int score = 100 / jsonArray.size();
            if ( i == jsonArray.size() - 1 ) {
                score = 100 - score * i;
            }
            String input = testCase.getString("input");
            String output = testCase.getString("output");

            NewCheckpoint checkpoint = new NewCheckpoint(problemId, i, isExactlyMatch, score, input, output);
            checkpointMapper.createCheckpoint(checkpoint);
        }
    }


    /**
     * 创建【试题所属分类】
     * @param problemId - 试题的唯一标识符
     * @param problemCategories - 试题分类别名的JSON数组
     */
    private void createProblemCategoryRelationships(long problemId, String problemCategories) {
        JSONArray jsonArray = JSON.parseArray(problemCategories);

        if ( jsonArray.size() == 0 ) {
            jsonArray.add("uncategorized");
        }
        for ( int i = 0; i < jsonArray.size(); ++ i ) {
            String problemCategorySlug = jsonArray.getString(i);
            ProblemCategory pc = problemCategoryMapper.getProblemCategoryUsingCategorySlug(problemCategorySlug);

            problemCategoryMapper.createProblemCategoryRelationship(problemId, pc);
        }
    }
    /**
     * 创建试题标签.
     * @param problemId - 试题的唯一标识符
     * @param problemTags - 试题标签的JSON数组
     */
    private void createProblemTags(long problemId, String problemTags) {
        Set<String> problemTagSlugs = new HashSet<>();
        JSONArray jsonArray = JSON.parseArray(problemTags);

        for ( int i = 0; i < jsonArray.size(); ++ i ) {
            String problemTagName = jsonArray.getString(i);
            String problemTagSlug = SlugifyUtils.getSlug(problemTagName);

            ProblemTag pt = problemTagMapper.getProblemTagUsingTagSlug(problemTagSlug);
            if ( pt == null ) {
                pt = new ProblemTag(problemTagSlug, problemTagName);
                problemTagMapper.createProblemTag(pt);
            }
            // Fix Bug: Two tags have different tag name but the same tag slug.
            // Example: Hello World / Hello-World
            if ( !problemTagSlugs.contains(problemTagSlug) ) {
                problemTagMapper.createProblemTagRelationship(problemId, pt);
                problemTagSlugs.add(problemTagSlug);
            }
        }
    }


    private void createProblemFile(long problemId, String problemFiles) {
        List<ProblemFile> problemFileList = JSONObject.parseArray(problemFiles, ProblemFile.class);
        Date now = new Date();
        LOGGER.info("problemFileList: " + problemFileList);
        if(problemFileList == null) {
            return;
        }
        for(int i = 0; i < problemFileList.size(); i++) {
            ProblemFile problemFile = problemFileList.get(i);
            problemFile.setId(UuidUtil.getShortUuid());
            problemFile.setProblemId(problemId);
            problemFile.setCreatedAt(now);
            problemFile.setUpdatedAt(now);
            int result = problemFileMapper.createProblemFile(problemFile);
            if(result !=0 ) {
                LOGGER.info(String.format("Create ProblemFile [%s] Successfully...", problemFile.toString()));
            }
        }
    }





    /**
     * 更新测试用例.
     * 首先删除该试题的全部的测试用例, 然后创建测试用例.
     * @param problemId - 试题的唯一标识符
     * @param testCases - 测试用例 (JSON格式)
     * @param isExactlyMatch - 是否精确匹配测试用例
     */
    private void updateTestCases(long problemId, String testCases, boolean isExactlyMatch) {
        checkpointMapper.deleteCheckpoint(problemId);
        createTestCases(problemId, testCases, isExactlyMatch);
    }


    /**
     * 更新试题所属分类.
     * 首先删除该试题的全部分类, 然后重新创建分类关系.
     * @param problemId - 试题的唯一标识符
     * @param problemCategories - 试题分类别名的JSON数组
     */
    private void updateProblemCategoryRelationships(long problemId, String problemCategories) {
        problemCategoryMapper.deleteProblemCategoryRelationship(problemId);
        createProblemCategoryRelationships(problemId, problemCategories);
    }

    /**
     * 更新【题目-语言-时间内存限制】
     * @param id
     * @param languageList
     */
    private void updateProblemLanguage(Long id, List<VojProblemLanguageParam> languageList) {
        if (CollectionUtil.isEmpty(languageList)){
            throw new BusinessException(BusinessExceptionCode.LIST_NULL);
        }
        QueryWrapper<VojProblemLanguage> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("problem_id", id);
        vojProblemLanguageMapper.delete(queryWrapper);
        languageList.stream().forEach(item -> {
            VojProblemLanguage vojProblemLanguage = BeanUtil.copyProperties(item, VojProblemLanguage.class);
            vojProblemLanguage.setProblemId(id);
            vojProblemLanguageMapper.insert(vojProblemLanguage);
        });
    }

    /**
     * 更新试题标签.
     * 首先删除该试题的全部标签, 然后重新创建标签与试题的关系.
     * @param problemId - 试题的唯一标识符
     * @param problemTags - 试题标签的JSON数组
     */
    private void updateProblemTags(long problemId, String problemTags) {
        problemTagMapper.deleteProblemTagRelationship(problemId);
        createProblemTags(problemId, problemTags);
    }

    /**
     * 获取试题列表
     * @return 试题列表(List<TrainingProblem>对象)
     */
    @Override
    public Page<ProblemViewDto> list(VojProblemQueryParam param) {
        Long companyId;
        long offset = param.getOffset();
        UserCache userInfo = UserInfoShareHolder.getUserInfo();
        User user = userInfo.getUser();
        if (UserIdentifyEnum.superAdmin.getIdentify().equals(user.getIdentify())) {
            companyId = null;
        }else if(UserIdentifyEnum.normalUser.getIdentify().equals(user.getIdentify())){
            param.setIsPublic(ProblemPublicConstant.PUBLIC);
            companyId = userInfo.getCompanyId();
        }else {
            companyId = userInfo.getCompanyId();
        }
        // 如果前端传来是 “All”, 那么不对这个选择条件进行添加，将字段设置为 null 或 ""
        if(param.getTagId() != null && param.getTagId().equals("All")) {
            param.setTagId("");
        }
        // 如果搜索框传来的是题号，将题号数据存入到 param 中
        if(param.getKeyword() != null && param.getKeyword().matches("^\\d+$") && param.getKeyword().length() <= 8){
            param.setProblemId(Integer.parseInt(param.getKeyword()));
        }
        Integer total = vojProblemsMapper.countList(param, companyId);
        List<ProblemViewDto> data = vojProblemsMapper.list(param, user.getId(), companyId, offset);
        if (!CollectionUtil.isEmpty(data)){
            Map<Integer, String> companyMap = redisUtil.getMap(RedisKey.COMPANY_ID_NAME);
            data.stream().forEach(item -> {
                // 补充公司名称
                Integer itemCompanyId = Integer.valueOf(item.getCompanyId().toString());
                if (companyMap.containsKey(itemCompanyId)){
                    item.setCompanyName(companyMap.get(itemCompanyId));
                }
                // 补充语言-时间内存限制
                List<VojProblemLanguageDto> vojProblemLanguages = vojProblemLanguageMapper.getLanguageByProblemId(item.getProblemId());
                item.setLanguageList(vojProblemLanguages);
            });
        }
        Page<ProblemViewDto> result = new Page<>(param.getCurrent(), param.getSize(), (null == total ? Constants.ZERO_CONSTANT : total));
        result.setRecords(data);
        return result;
    }

    @Override
    public TrainingProblem detail(Long id) {
        TrainingProblem result = vojProblemsMapper.detail(id);
        List<VojProblemLanguageDto> vojProblemLanguages = vojProblemLanguageMapper.getLanguageByProblemId(result.getProblemId());
        result.setLanguageList(vojProblemLanguages);
        UserCache userInfo = UserInfoShareHolder.getUserInfo();
        User user = userInfo.getUser();
        if(!UserIdentifyEnum.normalUser.getIdentify().equals(user.getIdentify())){
            // 非普通用户，需要展示测试用例
            QueryWrapper<VojProblemCheckpoints> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("problem_id", id);
            List<VojProblemCheckpoints> checkpointsList = vojProblemCheckpointsMapper.selectList(queryWrapper);
            result.setTestcase(checkpointsList);
        }
        return result;
    }


    /**
     * [此方法仅供管理员使用]
     * 获取某个试题的测试数据集.
     * @param problemId - 试题的唯一标识符
     * @return 某个试题的测试数据列表
     */
    @Override
    public List<NewCheckpoint> getCheckpointsUsingProblemId(long problemId) {
        List<NewCheckpoint> checkpoints = checkpointMapper.getCheckpointsUsingProblemId(problemId);
        return checkpoints;
    }

    @Override
    public List<ProblemViewDto> getProblemByIds(List<Long> ids) {
        List<ProblemViewDto> data = vojProblemsMapper.getProblemByIds(ids);
        if (!CollectionUtil.isEmpty(data)){
            Map<String, String> companyMap = redisUtil.getMap(RedisKey.COMPANY_ID_NAME);
            data.stream().forEach(item -> {
                // 补充公司名称
                Integer itemCompanyId = Integer.valueOf(item.getCompanyId().toString());
                if (companyMap.containsKey(itemCompanyId)){
                    item.setCompanyName(companyMap.get(itemCompanyId));
                }
                // 补充语言-时间内存限制
                List<VojProblemLanguageDto> vojProblemLanguages = vojProblemLanguageMapper.getLanguageByProblemId(item.getProblemId());
                item.setLanguageList(vojProblemLanguages);
            });
        }
        return data;
    }

    @Override
    public List<ProgrammingQuestionDTO> getProgrammingByRandom(Integer limit) {
        List<ProgrammingQuestionDTO> dto = vojProblemsMapper.getProgrammingByRandom(limit);
        if (limit > dto.size()) {
            throw ExceptionUtils.exception("题库中题目数量不足，不足以创建%s道编程题", limit);
        }
        Map<String, String> companyMap = redisUtil.getMap(RedisKey.COMPANY_ID_NAME);
        dto.forEach(e -> {
            Integer itemCompanyId = Integer.valueOf(e.getCompanyId().toString());
            if (companyMap.containsKey(itemCompanyId)) {
                e.setCompanyName(companyMap.get(itemCompanyId));
            }
            List<VojProblemLanguageDto> vojProblemLanguages = vojProblemLanguageMapper.getLanguageByProblemId(e.getProblemId());
            e.setLanguageList(vojProblemLanguages);
        });
        return dto;
    }

    @Override
    public List<ProgrammingQuestionDTO> getByCreatorId(Long creatorId, Long companyId) {
        if (creatorId == null || companyId == null) {
            return Collections.emptyList();
        }
        return vojProblemsMapper.getByCreatorId(creatorId, companyId);
    }

}
